/*
 * preprocesador.c
 *
 *  Created on: 02/09/2011
 *      Author: guido
 */

#define _GNU_SOURCE

#include <string.h>
#include <unistd.h>
#include "preprocesador.h"
#include "listaDefiniciones.h"

static const char FIN_LINEA_Y_ESPACIO[3] = { ' ', 10, 0 };
static const char FIN_LINEA[2] = { 10, 0 };
static const char CARACTERES_SEPARACION[13] = { ' ', '*', '+', '-', '/', '%', '<', '>', '=', '(', ')', 10, 0 };

void Prepro_init(Prepro* p) {
	/*ifs es la cantidad de directivas ifdef/ndef abiertas
	 * al momento */
	p->ifs = 0;
	p->elseAbierto = 0;
	/*omitirCodigo se usa para omitir copiar al buffer lineas
	 * omitidas por estructuras de control de directivas (ifdef,
	 * ifndef y else) */
	p->omitirCodigo = 0;
	//Ruta se hace para guardar temporalmente archivos nuevos para abrir
	p->ruta[0] = 0;
	ListaDef_init(&(p->definiciones));
}

/* Procesa las directivas de una linea pasada por parametro
 * Primero la carga en la estructura
 *
 */
int Prepro_procesarDirectivas(Prepro* p, char* entrada) {
	Prepro_cargarLinea(p, entrada);
	int resultado = 0;
	resultado |= Prepro_procesarEndif(p);
	resultado |= Prepro_procesarElse(p);
	/*Las siguientes directivas solo se procesan si no se encuentran
	 * dentro de un bloque ifdef o ifndef de algo indefinido o
	 * definido respectivamente */
	if (!(p->omitirCodigo)) {
		resultado |= Prepro_procesarInclude(p);
		resultado |= Prepro_procesarDefine(p);
		resultado |= Prepro_procesarIfdef(p);
		resultado |= Prepro_procesarIfndef(p);
	}
	/*Si se encontro una directiva o se debe omitir codigo
	 * el buffer se vacia, sino reemplazo las definiciones */
	if ((p->hayDirectiva) || (p->omitirCodigo))
		p->linea[0] = 0;
	else {
		Prepro_reemplazarDefines(p);
	}
	return resultado;
}

//Si encuentra un numeral que no esta en la pos 0, retorna ERROR
int Prepro_validarLinea(char* entrada) {
	char* pos = strstr(entrada, "#");
	int resultado = 0;
	if ((pos != NULL) && (pos != (entrada)))
		resultado = 1;
	return (resultado);
}

/* Procesa directiva include. Retorna ARCHIVO_NUEVO si se encontra la
 * directiva para que la funcion que invoco,
 * abra un archivo nuevo */
int Prepro_procesarInclude(Prepro* p) {
	int resultado = 0;
	if (strcmp(INCLUDE, p->directiva.identificador) == 0) {
		p->hayDirectiva = 1;
		strcpy(p->ruta, get_current_dir_name());
		strcat(p->ruta, "/");
		char* nombreArchivo = strtok(p->directiva.primerParametro, "\"");
		strcat(p->ruta, nombreArchivo);
		resultado = CODIGO_ARCHIVO_NUEVO;
	}
	return (resultado);
}

/*Procesa directiva define*/
int Prepro_procesarDefine(Prepro* p) {
	if (strcmp(DEFINE, p->directiva.identificador) == 0) {

		p->hayDirectiva = 1;
		if (strlen(p->linea) > MAX_DE_LINEA
		)
			return 1;
		else {
			/*El primer parametro es el nombre (o label),
			 * el segundo parametro es el valor */
			ListaDef_agregarDef(&(p->definiciones), p->directiva.primerParametro, p->directiva.segundoParametro);
		}
	}
	return 0;
}

/*Procesa la directiva ifdef. Si encuentra una,
 * setea la directiva if. Setea omitirCodigo dependiendo si encontro
 * definido el campo como argumento */
int Prepro_procesarIfdef(Prepro* p) {
	if (strcmp(IFDEF, p->directiva.identificador) == 0) {
		int resultado = Prepro_setearDirectivaIf(p);
		if (resultado == 1)
			return 1;
		int indice = ListaDef_buscarDef(&(p->definiciones), p->directiva.primerParametro);
		if (indice < 0)
			p->omitirCodigo = 1;
		else
			p->omitirCodigo = 0;
	}
	return 0;
}

/*Procesa la directiva ifndef. Si encuentra una,
 * setea la directiva if. Setea omitirCodigo dependiendo si encontro
 * definido el campo como argumento */
int Prepro_procesarIfndef(Prepro* p) {
	if (strcmp(IFNDEF, p->directiva.identificador) == 0) {
		int resultado = Prepro_setearDirectivaIf(p);
		if (resultado == 1)
			return 1;
		int indice = ListaDef_buscarDef(&(p->definiciones), p->directiva.primerParametro);
		if (indice < 0)
			p->omitirCodigo = 0;
		else
			p->omitirCodigo = 1;
	}
	return 0;
}

/* Procesa directivas endif. Retorna error si hay mas
 * endif que ifdef/ndef abiertos */
int Prepro_procesarEndif(Prepro* p) {
	if (strcmp(ENDIF, p->directiva.identificador) == 0) {
		p->hayDirectiva = 1;
		p->omitirCodigo = 0;
		p->elseAbierto = 0;
		p->ifs--;
		if (p->ifs < 0)
			return 1;
	}
	return 0;
}

/* Procesa el Else. Retorna error si ya habia un else abierto */
int Prepro_procesarElse(Prepro* p) {
	if (strcmp(ELSE, p->directiva.identificador) == 0) {
		p->hayDirectiva = 1;
		if (p->elseAbierto)
			return 1;
		p->omitirCodigo = !(p->omitirCodigo);
		p->elseAbierto = 1;
	}
	return 0;
}

/* Intenta cargar en la estructura de Preprocesador, el nombre
 * de la directiva, el primero y segundo argumento. Si no habia
 * alguno de los campos mencionados, quedan apuntando a NULL */
void Prepro_cargarLinea(Prepro* p, char* entrada) {
	p->linea = entrada;
	p->hayDirectiva = 0;
	char temp[MAX_DE_LINEA] = { 0 };
	p->directiva.identificador[0] = 0;
	p->directiva.primerParametro[0] = 0;
	p->directiva.segundoParametro[0] = 0;
	strcpy(temp, p->linea);
	char* token = strtok(temp, FIN_LINEA_Y_ESPACIO);
	if (token != NULL
	)
		strcpy(p->directiva.identificador, token);
	token = strtok(NULL, FIN_LINEA_Y_ESPACIO);
	if (token != NULL
	)
		strcpy(p->directiva.primerParametro, token);
	token = strtok(NULL, FIN_LINEA);
	if (token != NULL
	)
		strcpy(p->directiva.segundoParametro, token);
}

/*Setea los casos de directiva ifdef/ndef
 * Retorna ERROR si: la cantidad de ifs es mayor al maximo
 * o si el parametro de la directiva esta vacio  */
int Prepro_setearDirectivaIf(Prepro* p) {
	p->elseAbierto = 0;
	p->hayDirectiva = 1;
	p->ifs++;
	if (p->ifs == 15)
		return 1;
	if (p->directiva.primerParametro[0] == 0)
		return 1;
	return 0;
}

/*Recorre la linea de p, reemplazando por las definiciones
 * agregadas a la estructura. Primero copia a una variable
 * temporal el buffer, luego inicializa en 0 el buffer,
 * y concatena todos los tokens, chequeando para cada uno
 * si esta definido. Si esta definido, reemplaza por el valor*/
void Prepro_reemplazarDefines(Prepro* p) {
	char token[MAX_TAMANYO_TOKEN];
	char temp[MAX_DE_LINEA];
	strcpy(temp, p->linea);
	p->linea[0] = 0;
	int resultadoTokenizar = Prepro_obtenerTokens(token, temp);
	while (resultadoTokenizar > 0) {
		if (resultadoTokenizar == 1) {
			int indice = ListaDef_buscarDef(&(p->definiciones), token);
			if (indice >= 0) {
				strcpy(token, p->definiciones.defines[indice].valor);
			}
		}
		strcat(p->linea, token);
		resultadoTokenizar = Prepro_obtenerTokens(token, temp);
	}
}

/* Esta funcion quita el token mas proximo desde la izquierda, y lo coloca en destino
 * Si retorna 0, la linea esta vacia,
 * 1, retorno un token con caracteres validos
 * 2, retorno un token con caracteres de separaciones
 */
int Prepro_obtenerTokens(char* destino, char* source) {
	if (source[0] == 0)
		return 0;

	int resultado = 0;
	int i = strspn(source, CARACTERES_SEPARACION);
	if (i == 0) {
		//Caracteres que no son de separacion
		int h = strcspn(source, CARACTERES_SEPARACION);
		strncpy(destino, source, h);
		destino[h] = 0;
		strcpy(source, source + h);
		resultado = 1;
	} else {
		// Caracteres de separacion
		strncpy(destino, source, i);
		destino[i] = 0;
		strcpy(source, source + i);
		resultado = 2;
	}
	return (resultado);
}
